﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GpgApi;

namespace WgpgCore
{
    /// <summary>
    /// This class manages all keys used by the application.
    /// </summary>
    public static class KeyManager
    {
        #region Refresh
        public delegate void RefreshEventHandler(RefreshStatus status);
        public static event RefreshEventHandler Refresh;

        public enum RefreshStatus
        {
            Start,
            End
        }

        private static RefreshStatus _status = RefreshStatus.End;
        public static RefreshStatus Status
        {
            get { return _status; }
            set
            {
                if (value == _status)
                    return;

                _status = value;
                if (Refresh != null)
                    Refresh(_status);
            }
        }
        #endregion Refresh

        public static ExternalReadOnlyObservableCollection<KeyNode> Keys { get; private set; }
        private static readonly ObservableCollection<KeyNode> _keys;

        static KeyManager()
        {
            _keys = new ObservableCollection<KeyNode>();
            Keys = new ExternalReadOnlyObservableCollection<KeyNode>(_keys);
            RefreshAll();
        }

        public static KeyNode GetKeyNodeById(KeyId keyId)
        {
            return _keys.Find(delegate(KeyNode key) { return key.Id == keyId; });
        }

        public static List<KeyId> GetKeyIdsByEmail(IList<Email> emails)
        {
            List<KeyId> ids = new List<KeyId>();

            foreach (KeyNode key in _keys)
            {
                if (key.MatchEmail(emails))
                    ids.Add(key.Id);
            }

            return ids;
        }

        /// <summary>
        /// Refreshes all keys.
        /// </summary>
        public static void RefreshAll()
        {
            Status = RefreshStatus.Start;

            List<Key> newKeys = GetKeys();

            // Add new keys and set new values to existing keys
            foreach (Key key in newKeys)
            {
                KeyNode node = GetKeyNodeById(key.Id);
                if (node == null)
                    _keys.Add(new KeyNode(key));
                else
                    node.SetKey(key);
            }

            // Remove old keys
            for (Int32 i = 0; i < _keys.Count; )
            {
                if (newKeys.Exists(delegate(Key key) { return key.Id == _keys[i].Id; }))
                    ++i;
                else
                    _keys.RemoveAt(i);
            }

            Status = RefreshStatus.End;
        }

        /// <summary>
        /// Refreshes the specified key.
        /// </summary>
        /// <param name="keyId"></param>
        public static void RefreshKey(KeyId keyId)
        {
            Status = RefreshStatus.Start;

            Key key = GetKey(keyId);

            for (Int32 i = 0; i < _keys.Count; ++i)
            {
                if (_keys[i].Id == keyId)
                {
                    if (key == null)
                        _keys.RemoveAt(i);
                    else
                        _keys[i].SetKey(key);

                    break;
                }
            }

            Status = RefreshStatus.End;
        }

        //-----------------------------------------------------------------------------------------

        /// <summary>
        /// Retrieves a specified key from GPG. It retrieves both the public and the private key.
        /// </summary>
        /// <param name="keyId">The keyId of the key to retrieve</param>
        /// <returns>The retrieved key or null if the key does not exist.</returns>
        private static Key GetKey(KeyId keyId)
        {
            List<KeyId> filter = new List<KeyId> { keyId };

            List<Key> keys = GetKeys(filter);
            if (keys == null || keys.Count == 0)
                return null;

            return keys[0];
        }

        /// <summary>
        /// Retrieves a list of specified keys or all keys if the parameter "keyids" is null.
        /// </summary>
        /// <param name="keyIds"></param>
        /// <returns>The list of the keys. The list is never null but may be empty.</returns>
        private static List<Key> GetKeys(List<KeyId> keyIds = null)
        {
            GpgListPublicKeys publicKeys = new GpgListPublicKeys(keyIds);
            publicKeys.Execute();

            GpgListSecretKeys secretKeys = new GpgListSecretKeys(keyIds);
            secretKeys.Execute();

            List<Key> publicList = (List<Key>)publicKeys.Keys;
            List<Key> secretList = (List<Key>)secretKeys.Keys;

            foreach (Key key in publicList)
                if (secretList.Exists(delegate(Key sec) { return sec.Id == key.Id; }))
                    key.Type |= KeyType.Secret;

            return (List<Key>)publicKeys.Keys;
        }
    }
}
